import struct
from array import array
from typing import List
from vm.instruction import OpCode
from vm.value import Value


class Chunk:
    def __init__(self, name: str):
        self.name = name
        self.code = array('B')
        self.values: List['Value'] = []
        self.lines = array('L')

    def write(self, byte: int, line: int):
        self.code.append(byte)
        self.lines.append(line)

    def add_constant(self, value: 'Value', line: int):
        self.values.append(value)
        self.write(len(self.values) - 1, line)

    def disassemble(self):
        print(f'=== {self.name} ===')

        offset = 0
        while offset < len(self.code):
            offset = self.disassemble_instruction(offset)

        print()

    def disassemble_instruction(self, offset: int):
        print('{:04}'.format(offset), end='')

        if offset > 0 and self.lines[offset] == self.lines[offset - 1]:
            print('   | ', end='')
        else:
            print('{:4} '.format(self.lines[offset]), end='')

        opcode = self.code[offset]
        match opcode:
            case OpCode.RETURN | OpCode.NEGATE \
                 | OpCode.ADD | OpCode.SUBTRACT | OpCode.MULTIPLY \
                 | OpCode.DIVIDE | OpCode.NIL | OpCode.TRUE \
                 | OpCode.FALSE | OpCode.NOT | OpCode.EQUAL \
                 | OpCode.GREATER | OpCode.LESS | OpCode.PRINT \
                 | OpCode.POP | OpCode.ESCAPE_VALUE | OpCode.METHOD:
                return self.simple_instruction(opcode, offset)

            case OpCode.CONSTANT | OpCode.DEFINE_GLOBAL \
                 | OpCode.GET_GLOBAL | OpCode.DEFINE_GLOBAL | OpCode.SET_GLOBAL \
                 | OpCode.GET_LOCAL | OpCode.CLASS | OpCode.GET_PROPERTY \
                 | OpCode.SET_PROPERTY | OpCode.GET_SUPER:
                return self.constant_instruction(opcode, offset)

            case OpCode.JUMP | OpCode.JUMP_IF_FALSE:
                return self.jump_instruction(opcode, offset)

            case OpCode.CALL | OpCode.SET_LOCAL | OpCode.GET_FREE_VALUE \
                 | OpCode.SET_FREE_VALUE | OpCode.GET_UPVALUE | OpCode.SET_UPVALUE \
                 | OpCode.INHERIT:
                return self.single_offset_instruction(opcode, offset)

            case OpCode.CLOSURE:
                return self.closure_instruction(opcode, offset)

            case _:
                raise ValueError(f'unrecognized op code {OpCode(opcode).name}')

    def simple_instruction(self, ins: int, offset: int):
        print(OpCode(ins).name)
        return offset + 1

    def constant_instruction(self, ins: int, offset: int):
        const_index = self.code[offset + 1]
        print("{:<16} {:4} '{}'".format(OpCode(ins).name, const_index, self.values[const_index]))

        return offset + 2

    def jump_instruction(self, ins: int, offset: int):
        jump_offset_bytes = [self.code[offset + 1], self.code[offset + 2]]
        jump_offset = struct.unpack('>h', array('B', jump_offset_bytes))[0]
        print("{:<16} {:4}".format(OpCode(ins).name, jump_offset))

        return offset + 3

    def single_offset_instruction(self, ins: int, offset: int):
        const_val = self.code[offset + 1]
        print("{:<16} {:4}".format(OpCode(ins).name, const_val))

        return offset + 2

    def closure_instruction(self, ins: int, offset: int):
        const_index = self.code[offset + 1]
        closure = self.values[const_index].func_value()
        print("{:<16} {:4} '{}'".format(OpCode(ins).name, const_index, self.values[const_index]))

        return offset + closure.upvalue_count * 2 + 2


